/*
asynchronous serial communications class
*/
#ifndef ASYNC_SERIAL_
#define ASYNC_SERIAL_

#include <thread>
#include <queue>
#include <unordered_map>
#include <stdint.h>

// forward declare pimpl
struct serialimpl;

// utility namespace
namespace utl {

	enum line_status {
		LS_CTS,   // clear to send signal changed state
		LS_DSR,   // data set ready signal changed
		LS_CD,    // carrier detect signal changed state
		LS_BREAK, // a break was detected in input
		LS_ERR,   // a line status error occurred
		LS_RING   // ring indicator detected
	};

	struct datum
	{
		datum(char* data, size_t length) : data_(data), length_(length) {}
		char* data_;
		size_t length_;
	};

	enum parity_type
	{
		PARITYTYPE_NONE,  // most common
		PARITYTYPE_ODD,
		PARITYTYPE_EVEN
	};

	// bits per packet/frame
	enum databits_type
	{
		DATA_5 = 5,
		DATA_6 = 6,
		DATA_7 = 7,
		DATA_8 = 8       // most common
	};

	enum stopbits_type
	{
		STOPBIT_1,       // most common
		STOPBIT_1_5,
		STOPBIT_2
	};

	enum flow_control_type
	{
		FLOWCONTROL_OFF,              // no flow control - worth trying to get something working
		FLOWCONTROL_HARDWARE_RTSCTS,  // For best performance if supported by modem/cable
		FLOWCONTROL_HARDWARE_DTRDSR,  // not very common hardware flow control
		FLOWCONTROL_XONXOFF           // if cable has no flow control pins connected
	};

	struct port_settings
	{
		port_settings(unsigned baud, databits_type databits = DATA_8, parity_type parity = PARITYTYPE_NONE,
			unsigned stopbits = STOPBIT_1, flow_control_type flowcontrol = FLOWCONTROL_HARDWARE_RTSCTS)
			: baud_rate_(baud), databits_(databits), parity_type_(parity), stopbits_(stopbits), flowcontrol_(flowcontrol) {}
		unsigned baud_rate_;       // speed in bits per second
		databits_type databits_;   // usually set to 8 (8 bits per packet/frame)
		parity_type parity_type_;
		unsigned stopbits_;
		flow_control_type flowcontrol_;
	};

	class async_serial
	{
	public:
		// construct using port and baudrate
		async_serial(int port, unsigned baudrate);
		// construct using port and port_settings data
		async_serial(int port, port_settings settings);
		// close port related system resources
		virtual ~async_serial();
		// open serial port and setup handling for data
		virtual bool open();
		// check if serial port open
		bool is_open() const;
		// close serial port
		virtual bool close();
		// write data to port
		bool write(const char* data, size_t length);
		// override this function to handle received data
		virtual void on_read(char* data, size_t length);
		// override this function to identify when data successfully sent
		virtual void on_write();
		// override this function to indentify serial port state changes
		virtual void on_status(const unsigned statechange, bool set = true);
		// // override this function for diagnostic information
		virtual void on_error(const std::string& error);
		// get line status
		inline unsigned get_status() const { return status_; }
		// helper to convert status to a descriptive string
		std::string get_status_string();
		// helper function to get list of ports available and any available string description
		static long enumerate_ports(std::unordered_map <uint32_t, std::string>& ports);

		async_serial(const async_serial&) = delete;
		async_serial& operator=(const async_serial&) = delete;

	private:
		// forward declare pimpl
		serialimpl* pimpl_;  // Handle object - pimpl

		std::thread reader_thread;  // thread to handle read events
		void reader_thread_func();  // read thread handler function
		bool closing_port_;        // to indicate port is in process of being closed
		int port_;                  // port indentifier
		unsigned status_;           // status indication

		void update_pin_states();   // updates internal pin status flags
		void update_error_states(unsigned long comm_event);  // reports errors if any

		port_settings settings_;
		bool configure_comport();         // initial port setup called in open

		std::thread writer_thread;  // thread to handle write events
		void writer_thread_func();  // write thread handler function

		std::queue<datum> write_queue_;  // data is queued for sending/(writing)

		inline bool job_queue_empty() const { return write_queue_.empty(); }
	};

}  // utl

#endif // ASYNC_SERIAL_